import { InjectRedis } from '@/decorators';
import { UopOrderDto } from '@/dtos';
import { UopPaymentTypeEnum } from '@/enums';
import { getCode, getNanoid, halt } from '@/utils';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
import { createHash, createHmac } from 'crypto';
import dayjs from 'dayjs';
import { Redis } from 'ioredis';

@Injectable()
export class UopService {
  @InjectRedis()
  private readonly redis: Redis;
  private readonly apiUrl: string;
  private readonly notifyUrl: string;
  private readonly returnUrl: string;
  private readonly appId: string;
  private readonly appKey: string;
  private readonly appSecret: string;
  private readonly sourceCode: string;
  private readonly platformId: string;
  private readonly tid: string;
  private readonly instMid: string;
  private readonly alipayMid: string;
  private readonly wechatMid: string;

  constructor(private readonly config: ConfigService) {
    this.apiUrl = this.config.get('uop.apiUrl');
    this.notifyUrl = this.config.get('uop.notifyUrl');
    this.returnUrl = this.config.get('uop.returnUrl');
    this.appId = this.config.get('uop.appId');
    this.appKey = this.config.get('uop.appKey');
    this.appSecret = this.config.get('uop.appSecret');
    this.sourceCode = this.config.get('uop.sourceCode');
    this.platformId = this.config.get('uop.platformId');
    this.tid = this.config.get('uop.tid');
    this.instMid = this.config.get('uop.instMid');
    this.alipayMid = this.config.get('uop.alipayMid');
    this.wechatMid = this.config.get('uop.wechatMid');
  }

  get NotifyUrl() {
    return this.notifyUrl;
  }

  get ReturnUrl() {
    return this.returnUrl;
  }

  async order({ type, totalAmount, ...dto }: UopOrderDto) {
    const { mid, url, ...params } = this.getPaymentInfo(type);

    const data = {
      requestTimestamp: dayjs().format('YYYY-MM-DD HH:mm:ss'),
      merOrderId: await this.generateOrderNo(),
      mid,
      tid: this.tid,
      instMid: this.instMid,
      totalAmount,
      ...params,
      ...dto,
    };

    const timestamp = dayjs().format('YYYYMMDDHHmmss');
    const nonce = await getNanoid(32);
    const content = JSON.stringify(data);
    const signature = await this.getSignature(timestamp, nonce, content);

    console.log(`请求地址`, `${this.apiUrl}${url}`, '\n');
    console.log(`原始参数`, data, '\n');
    console.log(`签名`, signature, '\n');

    return {
      no: data.merOrderId,
      url: `${this.apiUrl}${url}?authorization=OPEN-FORM-PARAM&appId=${this.appId}&timestamp=${timestamp}&nonce=${nonce}&content=${encodeURIComponent(content)}&signature=${encodeURIComponent(signature)}`,
    };
  }

  async query(no: string, type: UopPaymentTypeEnum) {
    const { mid } = this.getPaymentInfo(type);
    return await this.post('/v1/netpay/query', {
      mid,
      merOrderId: no,
    });
  }

  async generateOrderNo() {
    return `${this.sourceCode}${dayjs().format('YYYYMMDD')}${this.platformId}${await getCode(
      7,
    )}`;
  }

  async getToken() {
    let token = await this.redis.get(`uop:token`);
    if (!token) {
      const data = await this.getAccessToken();
      if (data.errCode !== '0000') {
        return halt(`获取AccessToken失败: ${data?.errInfo ?? '未知错误'}`);
      }
      token = data.accessToken;
      await this.redis.set(
        `uop:token`,
        token,
        'EX',
        Number(data.expiresIn) - 20,
      );
    }
    return token;
  }

  getPaymentInfo(type: UopPaymentTypeEnum) {
    const data: any = {
      url: `/v1/netpay`,
      mid: '',
    };
    if (type === UopPaymentTypeEnum.ALIPAY) {
      data.url += `/trade/h5-pay`;
      data.mid = this.alipayMid;
    } else if (type === UopPaymentTypeEnum.WECHAT) {
      data.url += `/wxpay/h5-to-minipay`;
      data.mid = this.wechatMid;
      // data.sceneType = 'IOS_WAP';
      // data.merAppName = '浙江文化产权交易所股份有限公司';
      // data.merAppId = 'sport.zjcaee.com';
    } else {
      return halt('暂不支持该支付方式');
    }
    return data;
  }

  private async post(url: string, data: any = {}, headers: any = {}) {
    return await axios
      .post(
        `${this.apiUrl}${url}`,
        {
          requestTimestamp: dayjs().format('YYYY-MM-DD HH:mm:ss'),
          tid: this.tid,
          instMid: this.instMid,
          ...data,
        },
        {
          headers: {
            Authorization: `OPEN-ACCESS-TOKEN AccessToken="${await this.getToken()}"`,
            ...headers,
          },
        },
      )
      .then((res) => res.data)
      .catch((e) => console.error(e?.response?.data));
  }

  private async getAccessToken() {
    const timestamp = dayjs().format('YYYYMMDDHHmmss');
    const nonce = await getNanoid(32);
    return await axios
      .post(`${this.apiUrl}/v1/token/access`, {
        appId: this.appId,
        timestamp,
        nonce,
        signMethod: 'SHA256',
        signature: await this.getSignature(timestamp, nonce),
      })
      .then((res) => res.data);
  }

  private async getSignature(
    timestamp: string,
    nonce: string,
    content?: string,
  ): Promise<string> {
    return new Promise((r) => {
      if (!!content) {
        content = createHash('sha256').update(content).digest('hex');
        const signature = createHmac('sha256', this.appKey)
          .update(`${this.appId}${timestamp}${nonce}${content}`)
          .digest('base64')
          .toString();
        r(signature);
      } else {
        const signature = createHash('sha256')
          .update(`${this.appId}${timestamp}${nonce}${this.appKey}`)
          .digest('hex')
          .toString();
        r(signature);
      }
    });
  }
}
